package com.tangsm.spring.boot.async.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 任务执行器配置
 *
 * @author tangsm
 */
@EnableAsync
@Configuration
public class ThreadPoolConfig {
    /**
     * 核心线程数
     */
    @Value("${thread.pool.core-pool-size:5}")
    private int corePoolSize;

    /**
     * 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
     */
    @Value("${thread.pool.max-pool-size:50}")
    private int maxPoolSize;

    /**
     * 缓存队列
     */
    @Value("${thread.pool.queue-capacity:500}")
    private int queueCapacity;

    /**
     * 许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
     */
    @Value("${thread.pool.keep-alive-seconds:60}")
    private int keepAliveSeconds;

    /**
     * 默认@Async异步线程池任务执行器
     *
     * @return 异步线程池任务执行器
     */
    @Bean(name = "defaultTaskExecutor")
    public Executor defaultTaskExecutor() {
        return newExecutor("defaultExecutor-");
    }

    /**
     * 其他线程池任务执行器
     *
     * <pre>
     * 多个线程池配置，@Async注解，使用系统默认或者自定义的线程池（代替默认线程池）。
     * 可在项目中设置多个线程池，在异步调用时，指明需要调用的线程池名称，如代码中的customTaskExecutor，
     * 使用的时候注解@Async("customTaskExecutor")就行。
     * </pre>
     *
     * @return 线程池任务执行器
     */
    @Bean(name = "customTaskExecutor")
    public Executor customTaskExecutor() {
        return newExecutor("customExecutor-");
    }

    /**
     * 实例线程池任务执行器
     * @param threadNamePrefix 线程名称前缀
     * @return 线程池任务执行器
     */
    public Executor newExecutor(String threadNamePrefix) {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数
        taskExecutor.setCorePoolSize(corePoolSize);
        // 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(maxPoolSize);
        // 缓存队列
        taskExecutor.setQueueCapacity(queueCapacity);
        // 许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        // 异步方法内部线程名称
        taskExecutor.setThreadNamePrefix(threadNamePrefix);
        /*
         * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize，如果还有任务到来就会采取任务拒绝策略
         * 通常有以下四种策略：
         * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
         * ThreadPoolExecutor.DiscardPolicy：也是丢弃任务，但是不抛出异常。
         * ThreadPoolExecutor.DiscardOldestPolicy：丢弃队列最前面的任务，然后重新尝试执行任务（重复此过程）
         * ThreadPoolExecutor.CallerRunsPolicy：重试添加当前的任务，自动重复调用 execute() 方法，直到成功
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        taskExecutor.initialize();
        return taskExecutor;
    }
}
